package iss.java.part1;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.File;

/**
 * Created by WeiZehao on 16/10/16.
 * 实现了两种插值算法：二次线性插值算法和立方卷积插值算法
 */

public class ResizeImage
{
    private String _inputImagePath;         // 输入图片路径
    private String _outputImagePath;        // 输出图片路径
    private int _width;                     // 缩放后宽度
    private int _height;                    // 缩放后高度
    private BufferedImage _inputImage;      // 输入图片
    private BufferedImage _outputImage;     // 输出图片
    private String _interpolationMethod;    // 插值方法

    /*构造函数*/
    public ResizeImage(String inputImagePath, String outputImagePath,
                       String interpolationMethod, int width, int height)
    {
        _inputImagePath = inputImagePath;
        _outputImagePath = outputImagePath;
        _interpolationMethod = interpolationMethod;
        _width = width;
        _height = height;
    }

    /*缩放图片并输出到指定路径*/
    public void resizeImage()
    {
        // 获得输入图片
        _inputImage = getBufferedImage(_inputImagePath);
        // 新建一个空的输出图片
        _outputImage = new BufferedImage(_width, _height, BufferedImage.TYPE_INT_RGB);

        // 用新建图片储存输入图片放缩后的图片信息
        if(_interpolationMethod == "Bilinear")
        {
            setImageRGB(_outputImage, biInterpolation(getImageRGB(_inputImage),
                    _inputImage.getWidth(), _inputImage.getHeight(), _width, _height));
        }
        else if(_interpolationMethod == "Spline")
        {
            setImageRGB(_outputImage, splineInterpolation(getImageRGB(_inputImage),
                    _inputImage.getWidth(), _inputImage.getHeight(), _width, _height));
        }

        // 输出放缩后的图片
        File outputImg = new File(_outputImagePath);
        try
        {
            ImageOutputStream outImgStream = ImageIO.createImageOutputStream(outputImg);
            ImageIO.write(_outputImage, "jpg", outImgStream);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    /*获取内存图片*/
    private BufferedImage getBufferedImage(String path)
    {
        File file = new File(path);
        try
        {
            BufferedImage img = ImageIO.read(file);
            return img;
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }// end method

    /*获得储存图像像素点ARGB信息的一维数组*/
    private int[] getImageRGB(BufferedImage img)
    {
        int[] imageRGB = new int[img.getHeight() * img.getWidth()];
        return img.getRGB(0, 0, img.getWidth(), img.getHeight(), imageRGB, 0, img.getWidth());
    }

    /*为图片的像素点设置ARGB值*/
    private BufferedImage setImageRGB(BufferedImage img, int[] rgbData)
    {
        img.setRGB(0, 0, img.getWidth(), img.getHeight(), rgbData, 0, img.getWidth());
        return img;
    }

    /*将图片所有像素点的ARGB值放入一维数组中储存,每个像素点的ARGB值储存在一个Int中*/
    private int[] toOneIntRGB(int[][][] rgbData, int row, int col)
    {
        int [] oneIntRGB = new int[row * col];
        // 记录一维数组索引位置
        int count = 0;
        for(int a = 0; a < row; a++)
        {
            for(int b = 0; b < col; b++)
            {
                // 将ARGB值放入一个32bit的Int中
                oneIntRGB[count] = ((rgbData[a][b][0] << 24) & 0xFF000000)  // Alpha
                        | ((rgbData[a][b][1] << 16) & 0x00FF0000)           // Red
                        | ((rgbData[a][b][2] << 8) & 0x0000FF00)            // Green
                        | ((rgbData[a][b][3]) & 0x000000FF);                // Blue
                count++;
            }
        }
        return oneIntRGB;
    }// end method

    /*将图片像素点的ARGB值放入三维数组中储存,每个像素点的ARGB值分别储存在四个Int中*/
    private int[][][] toThreeIntRGB(int[] rgbData, int row, int col)
    {
        int[][][] threeIntRGB = new int[row][col][4];
        // 记录一维数组索引位置
        int count = 0;
        for(int a = 0; a < row; a++)
        {
            for(int b = 0; b < col; b++)
            {
                // 将存在一个Int中的RGB值分别存入四个Int中
                threeIntRGB[a][b][0] = (rgbData[count] >> 24) & 0xFF;   // Alpha
                threeIntRGB[a][b][1] = (rgbData[count] >> 16) & 0xFF;   // Red
                threeIntRGB[a][b][2] = (rgbData[count] >> 8) & 0xFF;    // Green
                threeIntRGB[a][b][3] = (rgbData[count]) & 0xFF;         // Blue
                count++;
            }
        }
        return threeIntRGB;
    }// end method

    /*方法一：二次线性插值算法*/
    private int[] biInterpolation(int[] originRGB, int originW, int originH, int resizeW, int resizeH)
    {
        // 将原图片所有像素点的ARGB值放入三维数组保存
        int[][][] originRGBThree = toThreeIntRGB(originRGB, originH, originW);

        // 将用三维数组保存缩放后图片所有像素点的ARGB值
        int[][][] resizeRGBThree = new int[resizeH][resizeW][4];

        // 按照行列计算缩放比例
        double rowRatio = ((double)originH / (double)resizeH);      // 行坐标比例
        double colRatio = ((double)originW / (double)resizeW);      // 列坐标比例
        int row;                                                    // 新图行坐标
        int col = 0;                                                // 新图旧坐标
        int maxR = originH - 1;                                     // 最大映射行坐标
        int maxC = originW - 1;                                     // 最大映射列坐标

        // 生成新图所有像素点ARGB值
        for(row = 0; row < resizeH; row++)
        {
            // 计算新图映射在原图中的行坐标
            double oriRow = row * rowRatio;
            // 原图行坐标整数部分
            double r = Math.floor(oriRow);
            // 原图行坐标小数部分
            double rf = oriRow - r;

            for(col = 0; col < resizeW; col++)
            {
                // 计算新图映射在原图中的列坐标
                double oriCol = col * colRatio;
                // 原图列坐标整数部分
                double c = Math.floor(oriCol);
                // 原图列坐标小数部分
                double cf = oriCol - c;

                // 二次线性插值算法中的四个计算因子
                double p1 = (1.0d - rf) * (1.0d - cf);
                double p2 = (1.0d - rf) * cf;
                double p3 = rf * (1.0d - cf);
                double p4 = rf * cf;

                // 分别对一个像素点的ARGB值进行计算
                for(int i = 0; i < 4; i++)
                {
                    resizeRGBThree[row][col][i] = (int)(
                            p1 * originRGBThree[transInt(r,maxR,0)][transInt(c,maxC,0)][i]
                                    + p2 * originRGBThree[transInt(r,maxR,0)][transInt(c+1,maxC,0)][i]
                                    + p3 * originRGBThree[transInt(r+1,maxR,0)][transInt(c,maxC,0)][i]
                                    + p4 * originRGBThree[transInt(r+1,maxR,0)][transInt(c+1,maxC,0)][i]);
                }
            }// end for
        }// end for

        // 将新图像所有像素点的ARGB值存入一维数组并返回
        return toOneIntRGB(resizeRGBThree, row, col);
    }// end method

    /*方法一、二所需：将行列坐标整数部分转为int型，并且做边界检测*/
    private int transInt(double x, int max, int min)
    {
        if(x > max)
        {
            return max;
        }
        else if (x < min)
        {
            return min;
        }
        else
        {
            return (int)x;
        }
    }

    /*方法二：立方卷积插值*/
    private int[] splineInterpolation(int[] originRGB, int originW, int originH, int resizeW, int resizeH)
    {
        // 将原图片所有像素点的ARGB值放入三维数组保存
        int[][][] originRGBThree = toThreeIntRGB(originRGB, originH, originW);

        // 将用三维数组保存缩放后图片所有像素点的ARGB值
        int[][][] resizeRGBThree = new int[resizeH][resizeW][4];

        // 按照行列计算缩放比例
        double rowRatio = ((double)originH / (double)resizeH);      // 行坐标比例
        double colRatio = ((double)originW / (double)resizeW);      // 列坐标比例
        int row;                                                    // 新图行坐标
        int col = 0;                                                // 新图旧坐标
        int maxR = originH - 1;                                     // 最大映射行坐标
        int maxC = originW - 1;                                     // 最大映射列坐标

        // 用于三次卷积计算的三个矩阵
        double[] matrixA = new double[4];
        double[][][] matrixB = new double[4][4][4];
        double[] matrixC = new double[4];

        // 生成新图所有像素点ARGB值
        for(row = 0; row < resizeH; row++)
        {
            // 计算新图映射在原图中的行坐标
            double oriRow = row * rowRatio;
            // 原图行坐标整数部分
            double r = Math.floor(oriRow);
            // 原图行坐标小数部分
            double rf = oriRow - r;

            // 为矩阵A赋值
            for(int i = 1, j = 0; i > -3; i--)
            {
                matrixA[j] = S2(rf + i);
                j++;
            }

            for(col = 0; col < resizeW; col++)
            {
                // 计算新图映射在原图中的列坐标
                double oriCol = col * colRatio;
                // 原图列坐标整数部分
                double c = Math.floor(oriCol);
                // 原图列坐标小数部分
                double cf = oriCol - c;

                // 为矩阵C赋值
                for(int i = 1, j = 0; i > -3; i--)
                {
                    matrixC[j] = S2(cf + i);
                    j++;
                }

                // 为矩阵B赋值
                // m n 表示原图中的行列坐标，若要得到（Row，Col）的ARGB值，需要获得原图中16个点的ARGB值
                // 将16个点的ARGB值都存入矩阵B
                for(double m = r - 1, a = 0; m <= r + 2; m++)
                {
                    for(double n = c - 1, b = 0; n <= c + 2; n++)
                    {
                        // 将一个像素点的ARGB四个值都存入矩阵B中
                        for(int x = 0; x < 4; x++)
                        {
                            matrixB[(int)a][(int)b][x] = originRGBThree[transInt(m,maxR,0)][transInt(n,maxC,0)][x];
                        }
                        b++;
                    }
                    a++;
                }

                // 为新图所有像素点赋ARGB值
                for(int i = 0; i < 4; i++)
                {
                    resizeRGBThree[row][col][i] = (int)matrixMulti(matrixA, matrixB, matrixC, i);
                }
            }// end for
        }// end for

        // 将新图像所有像素点的ARGB值存入一维数组并返回
        return toOneIntRGB(resizeRGBThree, row, col);
    }// end method

    /*方法二所需：样条数学采样公式*/
    private double S(double x)
    {
        if(x >= -1 && x <= 1)
        {
            return 2.0 / 3.0 + (1.0 / 2.0) * Math.pow(Math.abs(x),3.0) - Math.pow(x,2.0);
        }
        else if((x > 1 && x <= 2) || (x >= -2 && x <= -1))
        {
            return (1.0 / 6.0) * Math.pow(2.0 - Math.abs(x),3.0);
        }
        else
        {
            return 0;
        }
    }// end method

    /*方法二所需：lanczos插值函数*/
    private double S2(double x)
    {
        if(x == 0)
        {
            return 1.0;
        }
        else if(x >= 2 || x <= -2)
        {
            return 0.0;
        }
        else
        {
            return (Math.sin(Math.PI * x) / (Math.PI * x)) * (Math.sin(Math.PI * x / 2) / (Math.PI * x / 2));
        }
    }

    /*方法二所需：计算三矩阵相乘*/
    private double matrixMulti(double[] A, double[][][] B, double[] C, int ARGB)
    {
        // 计算矩阵A*B*C
        // 临时储存A*B的结果
        double[] temp = new double[4];

        // 计算A*B
        for(int i = 0; i < 4; i++)
        {
            for(int j = 0; j < 4; j++)
            {
                temp[i] = temp[i] + A[j] * B[j][i][ARGB];
            }
        }

        // 计算temp * C
        double result = 0.0;
        for(int i = 0; i < 4; i++)
        {
            result = result + temp[i] * C[i];
        }

        // 对RGB值做边界处理
        if(result < 0)
        {
            return 0.0;
        }
        else if(result > 255)
        {
            return 255.0;
        }
        else
        {
            return result;
        }
    }// end method
}
